package net.zf.base;

import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.ai.chat.client.*;
import org.springframework.ai.chat.client.advisor.ChatModelCallAdvisor;
import org.springframework.ai.chat.client.advisor.ChatModelStreamAdvisor;
import org.springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.chat.client.advisor.api.BaseAdvisorChain;
import org.springframework.ai.chat.client.observation.ChatClientObservationContext;
import org.springframework.ai.chat.client.observation.ChatClientObservationConvention;
import org.springframework.ai.chat.client.observation.ChatClientObservationDocumentation;
import org.springframework.ai.chat.client.observation.DefaultChatClientObservationConvention;
import org.springframework.ai.chat.messages.AbstractMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.content.Media;
import org.springframework.ai.converter.BeanOutputConverter;
import org.springframework.ai.converter.StructuredOutputConverter;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.template.TemplateRenderer;
import org.springframework.ai.template.st.StTemplateRenderer;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.MimeType;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;

import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.*;
import java.util.function.Consumer;

public class ZFChatClient implements ChatClient{

    private static final ChatClientObservationConvention DEFAULT_CHAT_CLIENT_OBSERVATION_CONVENTION = new DefaultChatClientObservationConvention();
    private static final TemplateRenderer DEFAULT_TEMPLATE_RENDERER = StTemplateRenderer.builder().build();
    private ZFChatClient.DefaultChatClientRequestSpec defaultChatClientRequest;

    public ZFChatClient(ZFChatClient.DefaultChatClientRequestSpec defaultChatClientRequest) {
        Assert.notNull(defaultChatClientRequest, "defaultChatClientRequest cannot be null");
        this.defaultChatClientRequest = defaultChatClientRequest;
    }

    public ZFChatClient.DefaultChatClientRequestSpec getDefaultChatClientRequest(){
        return this.defaultChatClientRequest;
    }

    public void setDefaultChatClientRequest(ZFChatClient.DefaultChatClientRequestSpec defaultChatClientRequest){
        this.defaultChatClientRequest = defaultChatClientRequest;
    }

    public ChatClient.ChatClientRequestSpec prompt() {
        return new ZFChatClient.DefaultChatClientRequestSpec(this.defaultChatClientRequest);
    }

    public ChatClient.ChatClientRequestSpec prompt(String content) {
        Assert.hasText(content, "content cannot be null or empty");
        return this.prompt(new Prompt(content));
    }

    public ChatClient.ChatClientRequestSpec prompt(Prompt prompt) {
        Assert.notNull(prompt, "prompt cannot be null");
        ZFChatClient.DefaultChatClientRequestSpec spec = new DefaultChatClientRequestSpec(this.defaultChatClientRequest);
        if (prompt.getOptions() != null) {
            spec.options(prompt.getOptions());
        }

        if (prompt.getInstructions() != null) {
            spec.messages(prompt.getInstructions());
        }

        return spec;
    }

    public ChatClient.Builder mutate() {
        return this.defaultChatClientRequest.mutate();
    }

    public static class DefaultChatClientRequestSpec implements ChatClient.ChatClientRequestSpec {
        private final ObservationRegistry observationRegistry;
        private final ChatClientObservationConvention observationConvention;
        private final ChatModel chatModel;
        private final List<Media> media;
        private final List<String> toolNames;
        private final List<ToolCallback> toolCallbacks;
        private final List<Message> messages;
        private final Map<String, Object> userParams;
        private final Map<String, Object> systemParams;
        private final List<Advisor> advisors;
        private final Map<String, Object> advisorParams;
        private final Map<String, Object> toolContext;
        private TemplateRenderer templateRenderer;
        @Nullable
        private String userText;
        @Nullable
        private String systemText;
        @Nullable
        private ChatOptions chatOptions;

        DefaultChatClientRequestSpec(ZFChatClient.DefaultChatClientRequestSpec ccr) {
            this(ccr.chatModel, ccr.userText, ccr.userParams, ccr.systemText, ccr.systemParams, ccr.toolCallbacks, ccr.messages, ccr.toolNames, ccr.media, ccr.chatOptions, ccr.advisors, ccr.advisorParams, ccr.observationRegistry, ccr.observationConvention, ccr.toolContext, ccr.templateRenderer);
        }

        public DefaultChatClientRequestSpec(ChatModel chatModel, @Nullable String userText, Map<String, Object> userParams, @Nullable String systemText, Map<String, Object> systemParams, List<ToolCallback> toolCallbacks, List<Message> messages, List<String> toolNames, List<Media> media, @Nullable ChatOptions chatOptions, List<Advisor> advisors, Map<String, Object> advisorParams, ObservationRegistry observationRegistry, @Nullable ChatClientObservationConvention observationConvention, Map<String, Object> toolContext, @Nullable TemplateRenderer templateRenderer) {
            this.media = new ArrayList();
            this.toolNames = new ArrayList();
            this.toolCallbacks = new ArrayList();
            this.messages = new ArrayList();
            this.userParams = new HashMap();
            this.systemParams = new HashMap();
            this.advisors = new ArrayList();
            this.advisorParams = new HashMap();
            this.toolContext = new HashMap();
            Assert.notNull(chatModel, "chatModel cannot be null");
            Assert.notNull(userParams, "userParams cannot be null");
            Assert.notNull(systemParams, "systemParams cannot be null");
            Assert.notNull(toolCallbacks, "toolCallbacks cannot be null");
            Assert.notNull(messages, "messages cannot be null");
            Assert.notNull(toolNames, "toolNames cannot be null");
            Assert.notNull(media, "media cannot be null");
            Assert.notNull(advisors, "advisors cannot be null");
            Assert.notNull(advisorParams, "advisorParams cannot be null");
            Assert.notNull(observationRegistry, "observationRegistry cannot be null");
            Assert.notNull(toolContext, "toolContext cannot be null");
            this.chatModel = chatModel;
            this.chatOptions = chatOptions != null ? chatOptions.copy() : (chatModel.getDefaultOptions() != null ? chatModel.getDefaultOptions().copy() : null);
            this.userText = userText;
            this.userParams.putAll(userParams);
            this.systemText = systemText;
            this.systemParams.putAll(systemParams);
            this.toolNames.addAll(toolNames);
            this.toolCallbacks.addAll(toolCallbacks);
            this.messages.addAll(messages);
            this.media.addAll(media);
            this.advisors.addAll(advisors);
            this.advisorParams.putAll(advisorParams);
            this.observationRegistry = observationRegistry;
            this.observationConvention = observationConvention != null ? observationConvention : ZFChatClient.DEFAULT_CHAT_CLIENT_OBSERVATION_CONVENTION;
            this.toolContext.putAll(toolContext);
            this.templateRenderer = templateRenderer != null ? templateRenderer : ZFChatClient.DEFAULT_TEMPLATE_RENDERER;
        }

        @Nullable
        public String getUserText() {
            return this.userText;
        }

        public Map<String, Object> getUserParams() {
            return this.userParams;
        }

        @Nullable
        public String getSystemText() {
            return this.systemText;
        }

        public Map<String, Object> getSystemParams() {
            return this.systemParams;
        }

        @Nullable
        public ChatOptions getChatOptions() {
            return this.chatOptions;
        }

        public List<Advisor> getAdvisors() {
            return this.advisors;
        }

        public Map<String, Object> getAdvisorParams() {
            return this.advisorParams;
        }

        public List<Message> getMessages() {
            return this.messages;
        }

        public List<Media> getMedia() {
            return this.media;
        }

        public List<String> getToolNames() {
            return this.toolNames;
        }

        public List<ToolCallback> getToolCallbacks() {
            return this.toolCallbacks;
        }

        public Map<String, Object> getToolContext() {
            return this.toolContext;
        }

        public TemplateRenderer getTemplateRenderer() {
            return this.templateRenderer;
        }

        public ChatClient.Builder mutate() {
//            ZFChatClientBuilder builder = (ZFChatClientBuilder)ChatClient.
//                    builder(this.chatModel, this.observationRegistry, this.observationConvention)
//                    .defaultTemplateRenderer(this.templateRenderer)
//                    .defaultToolCallbacks(this.toolCallbacks)
//                    .defaultToolContext(this.toolContext)
//                    .defaultToolNames(StringUtils.toStringArray(this.toolNames));

            ZFChatClientBuilder builder = new ZFChatClientBuilder(chatModel,this.observationRegistry, this.observationConvention);

            builder.defaultTemplateRenderer(this.templateRenderer);
            builder.defaultToolCallbacks(this.toolCallbacks);
            builder.defaultToolContext(this.toolContext);
            builder.defaultToolNames(StringUtils.toStringArray(this.toolNames));

            if (StringUtils.hasText(this.userText)) {
                builder.defaultUser((u) -> {
                    u.text(this.userText).params(this.userParams).media((Media[])this.media.toArray(new Media[0]));
                });
            }

            if (StringUtils.hasText(this.systemText)) {
                builder.defaultSystem((s) -> {
                    s.text(this.systemText).params(this.systemParams);
                });
            }

            if (this.chatOptions != null) {
                builder.defaultOptions(this.chatOptions);
            }

            builder.addMessages(this.messages);
            return builder;
        }

        public ChatClient.ChatClientRequestSpec advisors(Consumer<ChatClient.AdvisorSpec> consumer) {
            Assert.notNull(consumer, "consumer cannot be null");
            ZFChatClient.DefaultAdvisorSpec advisorSpec = new ZFChatClient.DefaultAdvisorSpec();
            consumer.accept(advisorSpec);
            this.advisorParams.putAll(advisorSpec.getParams());
            this.advisors.addAll(advisorSpec.getAdvisors());
            return this;
        }

        public ChatClient.ChatClientRequestSpec advisors(Advisor... advisors) {
            Assert.notNull(advisors, "advisors cannot be null");
            Assert.noNullElements(advisors, "advisors cannot contain null elements");
            this.advisors.addAll(Arrays.asList(advisors));
            return this;
        }

        public ChatClient.ChatClientRequestSpec advisors(List<Advisor> advisors) {
            Assert.notNull(advisors, "advisors cannot be null");
            Assert.noNullElements(advisors, "advisors cannot contain null elements");
            this.advisors.addAll(advisors);
            return this;
        }

        public ChatClient.ChatClientRequestSpec messages(Message... messages) {
            Assert.notNull(messages, "messages cannot be null");
            Assert.noNullElements(messages, "messages cannot contain null elements");
            this.messages.addAll(List.of(messages));
            return this;
        }

        public ChatClient.ChatClientRequestSpec messages(List<Message> messages) {
            Assert.notNull(messages, "messages cannot be null");
            Assert.noNullElements(messages, "messages cannot contain null elements");
            this.messages.addAll(messages);
            return this;
        }

        public <T extends ChatOptions> ChatClient.ChatClientRequestSpec options(T options) {
            Assert.notNull(options, "options cannot be null");
            this.chatOptions = options;
            return this;
        }

        public ChatClient.ChatClientRequestSpec toolNames(String... toolNames) {
            Assert.notNull(toolNames, "toolNames cannot be null");
            Assert.noNullElements(toolNames, "toolNames cannot contain null elements");
            this.toolNames.addAll(List.of(toolNames));
            return this;
        }

        public ChatClient.ChatClientRequestSpec toolCallbacks(ToolCallback... toolCallbacks) {
            Assert.notNull(toolCallbacks, "toolCallbacks cannot be null");
            Assert.noNullElements(toolCallbacks, "toolCallbacks cannot contain null elements");
            this.toolCallbacks.addAll(List.of(toolCallbacks));
            return this;
        }

        public ChatClient.ChatClientRequestSpec toolCallbacks(List<ToolCallback> toolCallbacks) {
            Assert.notNull(toolCallbacks, "toolCallbacks cannot be null");
            Assert.noNullElements(toolCallbacks, "toolCallbacks cannot contain null elements");
            this.toolCallbacks.addAll(toolCallbacks);
            return this;
        }

        public ChatClient.ChatClientRequestSpec tools(Object... toolObjects) {
            Assert.notNull(toolObjects, "toolObjects cannot be null");
            Assert.noNullElements(toolObjects, "toolObjects cannot contain null elements");
            this.toolCallbacks.addAll(Arrays.asList(ToolCallbacks.from(toolObjects)));
            return this;
        }

        public ChatClient.ChatClientRequestSpec toolCallbacks(ToolCallbackProvider... toolCallbackProviders) {
            Assert.notNull(toolCallbackProviders, "toolCallbackProviders cannot be null");
            Assert.noNullElements(toolCallbackProviders, "toolCallbackProviders cannot contain null elements");
            ToolCallbackProvider[] var2 = toolCallbackProviders;
            int var3 = toolCallbackProviders.length;

            for(int var4 = 0; var4 < var3; ++var4) {
                ToolCallbackProvider toolCallbackProvider = var2[var4];
                this.toolCallbacks.addAll(List.of(toolCallbackProvider.getToolCallbacks()));
            }

            return this;
        }

        public ChatClient.ChatClientRequestSpec toolContext(Map<String, Object> toolContext) {
            Assert.notNull(toolContext, "toolContext cannot be null");
            Assert.noNullElements(toolContext.keySet(), "toolContext keys cannot contain null elements");
            Assert.noNullElements(toolContext.values(), "toolContext values cannot contain null elements");
            this.toolContext.putAll(toolContext);
            return this;
        }

        public ChatClient.ChatClientRequestSpec system(String text) {
            Assert.hasText(text, "text cannot be null or empty");
            this.systemText = text;
            return this;
        }

        public ChatClient.ChatClientRequestSpec system(Resource text, Charset charset) {
            Assert.notNull(text, "text cannot be null");
            Assert.notNull(charset, "charset cannot be null");

            try {
                this.systemText = text.getContentAsString(charset);
                return this;
            } catch (IOException var4) {
                throw new RuntimeException(var4);
            }
        }

        public ChatClient.ChatClientRequestSpec system(Resource text) {
            Assert.notNull(text, "text cannot be null");
            return this.system(text, Charset.defaultCharset());
        }

        public ChatClient.ChatClientRequestSpec system(Consumer<ChatClient.PromptSystemSpec> consumer) {
            Assert.notNull(consumer, "consumer cannot be null");
            ZFChatClient.DefaultPromptSystemSpec systemSpec = new ZFChatClient.DefaultPromptSystemSpec();
            consumer.accept(systemSpec);
            this.systemText = StringUtils.hasText(systemSpec.text()) ? systemSpec.text() : this.systemText;
            this.systemParams.putAll(systemSpec.params());
            return this;
        }

        public ChatClient.ChatClientRequestSpec user(String text) {
            Assert.hasText(text, "text cannot be null or empty");
            this.userText = text;
            return this;
        }

        public ChatClient.ChatClientRequestSpec user(Resource text, Charset charset) {
            Assert.notNull(text, "text cannot be null");
            Assert.notNull(charset, "charset cannot be null");

            try {
                this.userText = text.getContentAsString(charset);
                return this;
            } catch (IOException var4) {
                throw new RuntimeException(var4);
            }
        }

        public ChatClient.ChatClientRequestSpec user(Resource text) {
            Assert.notNull(text, "text cannot be null");
            return this.user(text, Charset.defaultCharset());
        }

        public ChatClient.ChatClientRequestSpec user(Consumer<ChatClient.PromptUserSpec> consumer) {
            Assert.notNull(consumer, "consumer cannot be null");
            ZFChatClient.DefaultPromptUserSpec us = new ZFChatClient.DefaultPromptUserSpec();
            consumer.accept(us);
            this.userText = StringUtils.hasText(us.text()) ? us.text() : this.userText;
            this.userParams.putAll(us.params());
            this.media.addAll(us.media());
            return this;
        }

        public ChatClient.ChatClientRequestSpec templateRenderer(TemplateRenderer templateRenderer) {
            Assert.notNull(templateRenderer, "templateRenderer cannot be null");
            this.templateRenderer = templateRenderer;
            return this;
        }

        public ChatClient.CallResponseSpec call() {
            BaseAdvisorChain advisorChain = this.buildAdvisorChain();
            return new DefaultChatClient.DefaultCallResponseSpec(ZFChatClientUtils.toChatClientRequest(this), advisorChain, this.observationRegistry, this.observationConvention);
        }

        public ChatClient.StreamResponseSpec stream() {
            BaseAdvisorChain advisorChain = this.buildAdvisorChain();
            return new DefaultChatClient.DefaultStreamResponseSpec(ZFChatClientUtils.toChatClientRequest(this), advisorChain, this.observationRegistry, this.observationConvention);
        }

        private BaseAdvisorChain buildAdvisorChain() {
            this.advisors.add(ChatModelCallAdvisor.builder().chatModel(this.chatModel).build());
            this.advisors.add(ChatModelStreamAdvisor.builder().chatModel(this.chatModel).build());
            return DefaultAroundAdvisorChain.builder(this.observationRegistry).pushAll(this.advisors).templateRenderer(this.templateRenderer).build();
        }
    }

    public static class DefaultStreamResponseSpec implements ChatClient.StreamResponseSpec {
        private final ChatClientRequest request;
        private final BaseAdvisorChain advisorChain;
        private final ObservationRegistry observationRegistry;
        private final ChatClientObservationConvention observationConvention;

        public DefaultStreamResponseSpec(ChatClientRequest chatClientRequest, BaseAdvisorChain advisorChain, ObservationRegistry observationRegistry, ChatClientObservationConvention observationConvention) {
            Assert.notNull(chatClientRequest, "chatClientRequest cannot be null");
            Assert.notNull(advisorChain, "advisorChain cannot be null");
            Assert.notNull(observationRegistry, "observationRegistry cannot be null");
            Assert.notNull(observationConvention, "observationConvention cannot be null");
            this.request = chatClientRequest;
            this.advisorChain = advisorChain;
            this.observationRegistry = observationRegistry;
            this.observationConvention = observationConvention;
        }

        private Flux<ChatClientResponse> doGetObservableFluxChatResponse(ChatClientRequest chatClientRequest) {
            return Flux.deferContextual((contextView) -> {
                ChatClientObservationContext observationContext = ChatClientObservationContext.builder().request(chatClientRequest).advisors(this.advisorChain.getStreamAdvisors()).stream(true).build();
                Observation observation = ChatClientObservationDocumentation.AI_CHAT_CLIENT.observation(this.observationConvention, ZFChatClient.DEFAULT_CHAT_CLIENT_OBSERVATION_CONVENTION, () -> {
                    return observationContext;
                }, this.observationRegistry);
                observation.parentObservation((Observation)contextView.getOrDefault("micrometer.observation", (Object)null)).start();
                Flux var10000 = this.advisorChain.nextStream(chatClientRequest);
                Objects.requireNonNull(observation);
//                return var10000.doOnError(observation::error).doFinally((s) -> {
//                    observation.stop();
//                }).contextWrite((ctx) -> {
//                    return ctx.put("micrometer.observation", observation);
//                });

                return var10000;
            });
        }

        public Flux<ChatClientResponse> chatClientResponse() {
            return this.doGetObservableFluxChatResponse(this.request);
        }

        public Flux<ChatResponse> chatResponse() {
            return this.doGetObservableFluxChatResponse(this.request).mapNotNull(ChatClientResponse::chatResponse);
        }

        public Flux<String> content() {
            return this.doGetObservableFluxChatResponse(this.request).mapNotNull(ChatClientResponse::chatResponse).map((r) -> {
                return r.getResult() != null && r.getResult().getOutput() != null && r.getResult().getOutput().getText() != null ? r.getResult().getOutput().getText() : "";
            }).filter(StringUtils::hasLength);
        }
    }

    public static class DefaultCallResponseSpec implements ChatClient.CallResponseSpec {
        private final ChatClientRequest request;
        private final BaseAdvisorChain advisorChain;
        private final ObservationRegistry observationRegistry;
        private final ChatClientObservationConvention observationConvention;

        public DefaultCallResponseSpec(ChatClientRequest chatClientRequest, BaseAdvisorChain advisorChain, ObservationRegistry observationRegistry, ChatClientObservationConvention observationConvention) {
            Assert.notNull(chatClientRequest, "chatClientRequest cannot be null");
            Assert.notNull(advisorChain, "advisorChain cannot be null");
            Assert.notNull(observationRegistry, "observationRegistry cannot be null");
            Assert.notNull(observationConvention, "observationConvention cannot be null");
            this.request = chatClientRequest;
            this.advisorChain = advisorChain;
            this.observationRegistry = observationRegistry;
            this.observationConvention = observationConvention;
        }

        public <T> ResponseEntity<ChatResponse, T> responseEntity(Class<T> type) {
            Assert.notNull(type, "type cannot be null");
            return this.doResponseEntity(new BeanOutputConverter(type));
        }

        public <T> ResponseEntity<ChatResponse, T> responseEntity(ParameterizedTypeReference<T> type) {
            Assert.notNull(type, "type cannot be null");
            return this.doResponseEntity(new BeanOutputConverter(type));
        }

        public <T> ResponseEntity<ChatResponse, T> responseEntity(StructuredOutputConverter<T> structuredOutputConverter) {
            Assert.notNull(structuredOutputConverter, "structuredOutputConverter cannot be null");
            return this.doResponseEntity(structuredOutputConverter);
        }

        protected <T> ResponseEntity<ChatResponse, T> doResponseEntity(StructuredOutputConverter<T> outputConverter) {
            Assert.notNull(outputConverter, "structuredOutputConverter cannot be null");
            ChatResponse chatResponse = this.doGetObservableChatClientResponse(this.request, outputConverter.getFormat()).chatResponse();
            String responseContent = getContentFromChatResponse(chatResponse);
            if (responseContent == null) {
                return new ResponseEntity(chatResponse, (Object)null);
            } else {
                T entity = outputConverter.convert(responseContent);
                return new ResponseEntity(chatResponse, entity);
            }
        }

        @Nullable
        public <T> T entity(ParameterizedTypeReference<T> type) {
            Assert.notNull(type, "type cannot be null");
            BeanOutputConverter<T> outputConverter = new BeanOutputConverter(type);
            return this.doSingleWithBeanOutputConverter(outputConverter);
        }

        @Nullable
        public <T> T entity(StructuredOutputConverter<T> structuredOutputConverter) {
            Assert.notNull(structuredOutputConverter, "structuredOutputConverter cannot be null");
            return this.doSingleWithBeanOutputConverter(structuredOutputConverter);
        }

        @Nullable
        public <T> T entity(Class<T> type) {
            Assert.notNull(type, "type cannot be null");
            BeanOutputConverter<T> outputConverter = new BeanOutputConverter(type);
            return this.doSingleWithBeanOutputConverter(outputConverter);
        }

        @Nullable
        private <T> T doSingleWithBeanOutputConverter(StructuredOutputConverter<T> outputConverter) {
            ChatResponse chatResponse = this.doGetObservableChatClientResponse(this.request, outputConverter.getFormat()).chatResponse();
            String stringResponse = getContentFromChatResponse(chatResponse);
            return stringResponse == null ? null : outputConverter.convert(stringResponse);
        }

        public ChatClientResponse chatClientResponse() {
            return this.doGetObservableChatClientResponse(this.request);
        }

        @Nullable
        public ChatResponse chatResponse() {
            return this.doGetObservableChatClientResponse(this.request).chatResponse();
        }

        @Nullable
        public String content() {
            ChatResponse chatResponse = this.doGetObservableChatClientResponse(this.request).chatResponse();
            return getContentFromChatResponse(chatResponse);
        }

        private ChatClientResponse doGetObservableChatClientResponse(ChatClientRequest chatClientRequest) {
            return this.doGetObservableChatClientResponse(chatClientRequest, (String)null);
        }

        private ChatClientResponse doGetObservableChatClientResponse(ChatClientRequest chatClientRequest, @Nullable String outputFormat) {
            if (outputFormat != null) {
                chatClientRequest.context().put(ChatClientAttributes.OUTPUT_FORMAT.getKey(), outputFormat);
            }

            ChatClientObservationContext observationContext = ChatClientObservationContext.builder().request(chatClientRequest).advisors(this.advisorChain.getCallAdvisors()).stream(false).format(outputFormat).build();
            Observation observation = ChatClientObservationDocumentation.AI_CHAT_CLIENT.observation(this.observationConvention, ZFChatClient.DEFAULT_CHAT_CLIENT_OBSERVATION_CONVENTION, () -> {
                return observationContext;
            }, this.observationRegistry);
            ChatClientResponse chatClientResponse = (ChatClientResponse)observation.observe(() -> {
                return this.advisorChain.nextCall(chatClientRequest);
            });
            return chatClientResponse != null ? chatClientResponse : ChatClientResponse.builder().build();
        }

        @Nullable
        private static String getContentFromChatResponse(@Nullable ChatResponse chatResponse) {
            return (String)Optional.ofNullable(chatResponse).map(ChatResponse::getResult).map(Generation::getOutput).map(AbstractMessage::getText).orElse(null);
        }
    }

    public static class DefaultAdvisorSpec implements ChatClient.AdvisorSpec {
        private final List<Advisor> advisors = new ArrayList();
        private final Map<String, Object> params = new HashMap();

        public DefaultAdvisorSpec() {
        }

        public ChatClient.AdvisorSpec param(String key, Object value) {
            Assert.hasText(key, "key cannot be null or empty");
            Assert.notNull(value, "value cannot be null");
            this.params.put(key, value);
            return this;
        }

        public ChatClient.AdvisorSpec params(Map<String, Object> params) {
            Assert.notNull(params, "params cannot be null");
            Assert.noNullElements(params.keySet(), "param keys cannot contain null elements");
            Assert.noNullElements(params.values(), "param values cannot contain null elements");
            this.params.putAll(params);
            return this;
        }

        public ChatClient.AdvisorSpec advisors(Advisor... advisors) {
            Assert.notNull(advisors, "advisors cannot be null");
            Assert.noNullElements(advisors, "advisors cannot contain null elements");
            this.advisors.addAll(List.of(advisors));
            return this;
        }

        public ChatClient.AdvisorSpec advisors(List<Advisor> advisors) {
            Assert.notNull(advisors, "advisors cannot be null");
            Assert.noNullElements(advisors, "advisors cannot contain null elements");
            this.advisors.addAll(advisors);
            return this;
        }

        public List<Advisor> getAdvisors() {
            return this.advisors;
        }

        public Map<String, Object> getParams() {
            return this.params;
        }
    }

    public static class DefaultPromptSystemSpec implements ChatClient.PromptSystemSpec {
        private final Map<String, Object> params = new HashMap();
        @Nullable
        private String text;

        public DefaultPromptSystemSpec() {
        }

        public ChatClient.PromptSystemSpec text(String text) {
            Assert.hasText(text, "text cannot be null or empty");
            this.text = text;
            return this;
        }

        public ChatClient.PromptSystemSpec text(Resource text, Charset charset) {
            Assert.notNull(text, "text cannot be null");
            Assert.notNull(charset, "charset cannot be null");

            try {
                this.text(text.getContentAsString(charset));
                return this;
            } catch (IOException var4) {
                throw new RuntimeException(var4);
            }
        }

        public ChatClient.PromptSystemSpec text(Resource text) {
            Assert.notNull(text, "text cannot be null");
            this.text(text, Charset.defaultCharset());
            return this;
        }

        public ChatClient.PromptSystemSpec param(String key, Object value) {
            Assert.hasText(key, "key cannot be null or empty");
            Assert.notNull(value, "value cannot be null");
            this.params.put(key, value);
            return this;
        }

        public ChatClient.PromptSystemSpec params(Map<String, Object> params) {
            Assert.notNull(params, "params cannot be null");
            Assert.noNullElements(params.keySet(), "param keys cannot contain null elements");
            Assert.noNullElements(params.values(), "param values cannot contain null elements");
            this.params.putAll(params);
            return this;
        }

        @Nullable
        protected String text() {
            return this.text;
        }

        protected Map<String, Object> params() {
            return this.params;
        }
    }

    public static class DefaultPromptUserSpec implements ChatClient.PromptUserSpec {
        private final Map<String, Object> params = new HashMap();
        private final List<Media> media = new ArrayList();
        @Nullable
        private String text;

        public DefaultPromptUserSpec() {
        }

        public ChatClient.PromptUserSpec media(Media... media) {
            Assert.notNull(media, "media cannot be null");
            Assert.noNullElements(media, "media cannot contain null elements");
            this.media.addAll(Arrays.asList(media));
            return this;
        }

        public ChatClient.PromptUserSpec media(MimeType mimeType, URL url) {
            Assert.notNull(mimeType, "mimeType cannot be null");
            Assert.notNull(url, "url cannot be null");

            try {
                this.media.add(Media.builder().mimeType(mimeType).data(url.toURI()).build());
                return this;
            } catch (URISyntaxException var4) {
                throw new RuntimeException(var4);
            }
        }

        public ChatClient.PromptUserSpec media(MimeType mimeType, Resource resource) {
            Assert.notNull(mimeType, "mimeType cannot be null");
            Assert.notNull(resource, "resource cannot be null");
            this.media.add(Media.builder().mimeType(mimeType).data(resource).build());
            return this;
        }

        public ChatClient.PromptUserSpec text(String text) {
            Assert.hasText(text, "text cannot be null or empty");
            this.text = text;
            return this;
        }

        public ChatClient.PromptUserSpec text(Resource text, Charset charset) {
            Assert.notNull(text, "text cannot be null");
            Assert.notNull(charset, "charset cannot be null");

            try {
                this.text(text.getContentAsString(charset));
                return this;
            } catch (IOException var4) {
                throw new RuntimeException(var4);
            }
        }

        public ChatClient.PromptUserSpec text(Resource text) {
            Assert.notNull(text, "text cannot be null");
            this.text(text, Charset.defaultCharset());
            return this;
        }

        public ChatClient.PromptUserSpec param(String key, Object value) {
            Assert.hasText(key, "key cannot be null or empty");
            Assert.notNull(value, "value cannot be null");
            this.params.put(key, value);
            return this;
        }

        public ChatClient.PromptUserSpec params(Map<String, Object> params) {
            Assert.notNull(params, "params cannot be null");
            Assert.noNullElements(params.keySet(), "param keys cannot contain null elements");
            Assert.noNullElements(params.values(), "param values cannot contain null elements");
            this.params.putAll(params);
            return this;
        }

        @Nullable
        protected String text() {
            return this.text;
        }

        protected Map<String, Object> params() {
            return this.params;
        }

        protected List<Media> media() {
            return this.media;
        }
    }
}
